﻿using System;
using System.IO;
using GeoAPI;
using GeoAPI.Geometries;
using GeoAPI.IO;
using NetTopologySuite.Geometries;

namespace NetTopologySuite.IO
{
	public class WKBReader : IBinaryGeometryReader, IGeometryReader<byte[]>, IGeometryIOSettings
	{
		public static byte[] HexToBytes(string hex)
		{
			byte[] array = new byte[hex.Length / 2];
			for (int i = 0; i < hex.Length / 2; i++)
			{
				int num = 2 * i;
				if (num + 1 > hex.Length)
				{
					throw new ArgumentException("Hex string has odd length");
				}
				int num2 = WKBReader.HexToInt(hex[num]);
				int num3 = WKBReader.HexToInt(hex[num + 1]);
				array[i] = (byte)((num2 << 4) + (int)((byte)num3));
			}
			return array;
		}

		private static int HexToInt(char hex)
		{
			switch (hex)
			{
			case '0':
			case '1':
			case '2':
			case '3':
			case '4':
			case '5':
			case '6':
			case '7':
			case '8':
			case '9':
				return (int)(hex - '0');
			case ':':
			case ';':
			case '<':
			case '=':
			case '>':
			case '?':
			case '@':
				break;
			case 'A':
			case 'B':
			case 'C':
			case 'D':
			case 'E':
			case 'F':
				return (int)(hex - 'A' + '\n');
			default:
				switch (hex)
				{
				case 'a':
				case 'b':
				case 'c':
				case 'd':
				case 'e':
				case 'f':
					return (int)(hex - 'a' + '\n');
				}
				break;
			}
			throw new ArgumentException("Invalid hex digit: " + hex.ToString());
		}

		[Obsolete]
		protected IGeometryFactory Factory
		{
			get
			{
				return this._factory;
			}
		}

		public WKBReader() : this(GeometryServiceProvider.Instance)
		{
		}

		[Obsolete]
		public WKBReader(IGeometryFactory factory)
		{
			this._geometryServices = GeometryServiceProvider.Instance;
			this._factory = factory;
			this._sequenceFactory = factory.CoordinateSequenceFactory;
			this._precisionModel = factory.PrecisionModel;
			this.HandleSRID = true;
			this.HandleOrdinates = this.AllowedOrdinates;
		}

		public WKBReader(IGeometryServices services)
		{
			services = (services ?? GeometryServiceProvider.Instance);
			this._geometryServices = services;
			this._precisionModel = services.DefaultPrecisionModel;
			this._sequenceFactory = services.DefaultCoordinateSequenceFactory;
			this.HandleSRID = true;
			this.HandleOrdinates = this.AllowedOrdinates;
		}

		public IGeometry Read(byte[] data)
		{
			IGeometry result;
			using (Stream stream = new MemoryStream(data))
			{
				result = this.Read(stream);
			}
			return result;
		}

		public virtual IGeometry Read(Stream stream)
		{
			ByteOrder byteOrder = (ByteOrder)stream.ReadByte();
			BinaryReader binaryReader;
			if (byteOrder == ByteOrder.LittleEndian)
			{
				binaryReader = new BinaryReader(stream);
			}
			else if (byteOrder == ByteOrder.BigEndian)
			{
				binaryReader = new BEBinaryReader(stream);
			}
			else
			{
				if (this._isStrict)
				{
					throw new ParseException(string.Format("Unknown geometry byte order (not LittleEndian or BigEndian): {0}", byteOrder));
				}
				binaryReader = new BinaryReader(stream);
			}
			IGeometry result;
			using (binaryReader)
			{
				result = this.Read(binaryReader);
			}
			return result;
		}

		protected IGeometry Read(BinaryReader reader)
		{
			WKBReader.CoordinateSystem cs;
			int srid;
			WKBGeometryTypes wkbgeometryTypes = this.ReadGeometryType(reader, out cs, out srid);
			if (wkbgeometryTypes <= WKBGeometryTypes.WKBGeometryCollectionZ)
			{
				switch (wkbgeometryTypes)
				{
				case WKBGeometryTypes.WKBPoint:
					break;
				case WKBGeometryTypes.WKBLineString:
					goto IL_C1;
				case WKBGeometryTypes.WKBPolygon:
					goto IL_CB;
				case WKBGeometryTypes.WKBMultiPoint:
					goto IL_D5;
				case WKBGeometryTypes.WKBMultiLineString:
					goto IL_DF;
				case WKBGeometryTypes.WKBMultiPolygon:
					goto IL_E9;
				case WKBGeometryTypes.WKBGeometryCollection:
					goto IL_F3;
				default:
					switch (wkbgeometryTypes)
					{
					case WKBGeometryTypes.WKBPointZ:
						break;
					case WKBGeometryTypes.WKBLineStringZ:
						goto IL_C1;
					case WKBGeometryTypes.WKBPolygonZ:
						goto IL_CB;
					case WKBGeometryTypes.WKBMultiPointZ:
						goto IL_D5;
					case WKBGeometryTypes.WKBMultiLineStringZ:
						goto IL_DF;
					case WKBGeometryTypes.WKBMultiPolygonZ:
						goto IL_E9;
					case WKBGeometryTypes.WKBGeometryCollectionZ:
						goto IL_F3;
					default:
						goto IL_FD;
					}
					break;
				}
			}
			else
			{
				switch (wkbgeometryTypes)
				{
				case WKBGeometryTypes.WKBPointM:
					break;
				case WKBGeometryTypes.WKBLineStringM:
					goto IL_C1;
				case WKBGeometryTypes.WKBPolygonM:
					goto IL_CB;
				case WKBGeometryTypes.WKBMultiPointM:
					goto IL_D5;
				case WKBGeometryTypes.WKBMultiLineStringM:
					goto IL_DF;
				case WKBGeometryTypes.WKBMultiPolygonM:
					goto IL_E9;
				case WKBGeometryTypes.WKBGeometryCollectionM:
					goto IL_F3;
				default:
					switch (wkbgeometryTypes)
					{
					case WKBGeometryTypes.WKBPointZM:
						break;
					case WKBGeometryTypes.WKBLineStringZM:
						goto IL_C1;
					case WKBGeometryTypes.WKBPolygonZM:
						goto IL_CB;
					case WKBGeometryTypes.WKBMultiPointZM:
						goto IL_D5;
					case WKBGeometryTypes.WKBMultiLineStringZM:
						goto IL_DF;
					case WKBGeometryTypes.WKBMultiPolygonZM:
						goto IL_E9;
					case WKBGeometryTypes.WKBGeometryCollectionZM:
						goto IL_F3;
					default:
						goto IL_FD;
					}
					break;
				}
			}
			return this.ReadPoint(reader, cs, srid);
			IL_C1:
			return this.ReadLineString(reader, cs, srid);
			IL_CB:
			return this.ReadPolygon(reader, cs, srid);
			IL_D5:
			return this.ReadMultiPoint(reader, cs, srid);
			IL_DF:
			return this.ReadMultiLineString(reader, cs, srid);
			IL_E9:
			return this.ReadMultiPolygon(reader, cs, srid);
			IL_F3:
			return this.ReadGeometryCollection(reader, cs, srid);
			IL_FD:
			throw new ArgumentException("Geometry type not recognized. GeometryCode: " + wkbgeometryTypes);
		}

		private void ReadByteOrder(BinaryReader reader)
		{
			reader.ReadByte();
		}

		private WKBGeometryTypes ReadGeometryType(BinaryReader reader, out WKBReader.CoordinateSystem coordinateSystem, out int srid)
		{
			uint num = reader.ReadUInt32();
			if ((num & 3221225472u) == 3221225472u)
			{
				coordinateSystem = WKBReader.CoordinateSystem.XYZM;
			}
			else if ((num & 2147483648u) == 2147483648u)
			{
				coordinateSystem = WKBReader.CoordinateSystem.XYZ;
			}
			else if ((num & 1073741824u) == 1073741824u)
			{
				coordinateSystem = WKBReader.CoordinateSystem.XYM;
			}
			else
			{
				coordinateSystem = WKBReader.CoordinateSystem.XY;
			}
			if ((num & 536870912u) != 0u)
			{
				srid = reader.ReadInt32();
			}
			else
			{
				srid = -1;
			}
			if (!this.HandleSRID)
			{
				srid = -1;
			}
			switch ((num & 65535u) / 1000u)
			{
			case 1u:
				coordinateSystem = WKBReader.CoordinateSystem.XYZ;
				break;
			case 2u:
				coordinateSystem = WKBReader.CoordinateSystem.XYM;
				break;
			case 3u:
				coordinateSystem = WKBReader.CoordinateSystem.XYZM;
				break;
			}
			return (WKBGeometryTypes)((num & 65535u) % 1000u);
		}

		protected ICoordinateSequence ReadCoordinateSequence(BinaryReader reader, int size, WKBReader.CoordinateSystem cs)
		{
			ICoordinateSequence coordinateSequence = this._sequenceFactory.Create(size, WKBReader.ToOrdinates(cs));
			for (int i = 0; i < size; i++)
			{
				double num = reader.ReadDouble();
				double num2 = reader.ReadDouble();
				if (this._precisionModel != null)
				{
					num = this._precisionModel.MakePrecise(num);
				}
				if (this._precisionModel != null)
				{
					num2 = this._precisionModel.MakePrecise(num2);
				}
				coordinateSequence.SetOrdinate(i, Ordinate.X, num);
				coordinateSequence.SetOrdinate(i, Ordinate.Y, num2);
				switch (cs)
				{
				case WKBReader.CoordinateSystem.XY:
					break;
				case WKBReader.CoordinateSystem.XYZ:
				{
					double value = reader.ReadDouble();
					if (this.HandleOrdinate(Ordinate.Z))
					{
						coordinateSequence.SetOrdinate(i, Ordinate.Z, value);
					}
					break;
				}
				case WKBReader.CoordinateSystem.XYM:
				{
					double value2 = reader.ReadDouble();
					if (this.HandleOrdinate(Ordinate.M))
					{
						coordinateSequence.SetOrdinate(i, Ordinate.M, value2);
					}
					break;
				}
				case WKBReader.CoordinateSystem.XYZM:
				{
					double value = reader.ReadDouble();
					if (this.HandleOrdinate(Ordinate.Z))
					{
						coordinateSequence.SetOrdinate(i, Ordinate.Z, value);
					}
					double value2 = reader.ReadDouble();
					if (this.HandleOrdinate(Ordinate.M))
					{
						coordinateSequence.SetOrdinate(i, Ordinate.M, value2);
					}
					break;
				}
				default:
					throw new ArgumentException(string.Format("Coordinate system not supported: {0}", cs));
				}
			}
			return coordinateSequence;
		}

		protected ICoordinateSequence ReadCoordinateSequenceRing(BinaryReader reader, int size, WKBReader.CoordinateSystem cs)
		{
			ICoordinateSequence coordinateSequence = this.ReadCoordinateSequence(reader, size, cs);
			if (this._isStrict)
			{
				return coordinateSequence;
			}
			if (CoordinateSequences.IsRing(coordinateSequence))
			{
				return coordinateSequence;
			}
			return CoordinateSequences.EnsureValidRing(this._sequenceFactory, coordinateSequence);
		}

		protected ICoordinateSequence ReadCoordinateSequenceLineString(BinaryReader reader, int size, WKBReader.CoordinateSystem cs)
		{
			ICoordinateSequence coordinateSequence = this.ReadCoordinateSequence(reader, size, cs);
			if (this._isStrict)
			{
				return coordinateSequence;
			}
			if (coordinateSequence.Count == 0 || coordinateSequence.Count >= 2)
			{
				return coordinateSequence;
			}
			return CoordinateSequences.Extend(this._geometryServices.DefaultCoordinateSequenceFactory, coordinateSequence, 2);
		}

		private static Ordinates ToOrdinates(WKBReader.CoordinateSystem cs)
		{
			Ordinates ordinates = Ordinates.XY;
			if (cs == WKBReader.CoordinateSystem.XYM)
			{
				ordinates |= Ordinates.M;
			}
			if (cs == WKBReader.CoordinateSystem.XYZ)
			{
				ordinates |= Ordinates.Z;
			}
			if (cs == WKBReader.CoordinateSystem.XYZM)
			{
				ordinates |= (Ordinates.Z | Ordinates.M);
			}
			return ordinates;
		}

		protected ILinearRing ReadLinearRing(BinaryReader reader, WKBReader.CoordinateSystem cs, int srid)
		{
			IGeometryFactory geometryFactory = this._geometryServices.CreateGeometryFactory(this._precisionModel, srid, this._sequenceFactory);
			int size = reader.ReadInt32();
			ICoordinateSequence coordinates = this.ReadCoordinateSequenceRing(reader, size, cs);
			return geometryFactory.CreateLinearRing(coordinates);
		}

		protected IGeometry ReadPoint(BinaryReader reader, WKBReader.CoordinateSystem cs, int srid)
		{
			return this._geometryServices.CreateGeometryFactory(this._precisionModel, srid, this._sequenceFactory).CreatePoint(this.ReadCoordinateSequence(reader, 1, cs));
		}

		protected IGeometry ReadLineString(BinaryReader reader, WKBReader.CoordinateSystem cs, int srid)
		{
			IGeometryFactory geometryFactory = this._geometryServices.CreateGeometryFactory(this._precisionModel, srid, this._sequenceFactory);
			int size = reader.ReadInt32();
			ICoordinateSequence coordinates = this.ReadCoordinateSequenceLineString(reader, size, cs);
			return geometryFactory.CreateLineString(coordinates);
		}

		protected IGeometry ReadPolygon(BinaryReader reader, WKBReader.CoordinateSystem cs, int srid)
		{
			IGeometryFactory geometryFactory = this._geometryServices.CreateGeometryFactory(this._precisionModel, srid, this._sequenceFactory);
			ILinearRing shell = null;
			ILinearRing[] array = null;
			int num = reader.ReadInt32();
			if (num > 0)
			{
				shell = this.ReadLinearRing(reader, cs, srid);
				array = new ILinearRing[num - 1];
				for (int i = 0; i < num - 1; i++)
				{
					array[i] = this.ReadLinearRing(reader, cs, srid);
				}
			}
			return geometryFactory.CreatePolygon(shell, array);
		}

		protected IGeometry ReadMultiPoint(BinaryReader reader, WKBReader.CoordinateSystem cs, int srid)
		{
			IGeometryFactory geometryFactory = this._geometryServices.CreateGeometryFactory(this._precisionModel, srid, this._sequenceFactory);
			int num = reader.ReadInt32();
			IPoint[] array = new IPoint[num];
			for (int i = 0; i < num; i++)
			{
				this.ReadByteOrder(reader);
				WKBReader.CoordinateSystem cs2;
				int srid2;
				if (this.ReadGeometryType(reader, out cs2, out srid2) != WKBGeometryTypes.WKBPoint)
				{
					throw new ArgumentException("IPoint feature expected");
				}
				array[i] = (this.ReadPoint(reader, cs2, srid2) as IPoint);
			}
			return geometryFactory.CreateMultiPoint(array);
		}

		protected IGeometry ReadMultiLineString(BinaryReader reader, WKBReader.CoordinateSystem cs, int srid)
		{
			IGeometryFactory geometryFactory = this._geometryServices.CreateGeometryFactory(this._precisionModel, srid, this._sequenceFactory);
			int num = reader.ReadInt32();
			ILineString[] array = new ILineString[num];
			for (int i = 0; i < num; i++)
			{
				this.ReadByteOrder(reader);
				WKBReader.CoordinateSystem cs2;
				int srid2;
				if (this.ReadGeometryType(reader, out cs2, out srid2) != WKBGeometryTypes.WKBLineString)
				{
					throw new ArgumentException("ILineString feature expected");
				}
				array[i] = (this.ReadLineString(reader, cs2, srid2) as ILineString);
			}
			return geometryFactory.CreateMultiLineString(array);
		}

		protected IGeometry ReadMultiPolygon(BinaryReader reader, WKBReader.CoordinateSystem cs, int srid)
		{
			IGeometryFactory geometryFactory = this._geometryServices.CreateGeometryFactory(this._precisionModel, srid, this._sequenceFactory);
			int num = reader.ReadInt32();
			IPolygon[] array = new IPolygon[num];
			for (int i = 0; i < num; i++)
			{
				this.ReadByteOrder(reader);
				WKBReader.CoordinateSystem cs2;
				int srid2;
				if (this.ReadGeometryType(reader, out cs2, out srid2) != WKBGeometryTypes.WKBPolygon)
				{
					throw new ArgumentException("IPolygon feature expected");
				}
				array[i] = (this.ReadPolygon(reader, cs2, srid2) as IPolygon);
			}
			return geometryFactory.CreateMultiPolygon(array);
		}

		protected IGeometry ReadGeometryCollection(BinaryReader reader, WKBReader.CoordinateSystem cs, int srid)
		{
			IGeometryFactory geometryFactory = this._geometryServices.CreateGeometryFactory(this._precisionModel, srid, this._sequenceFactory);
			int num = reader.ReadInt32();
			IGeometry[] array = new IGeometry[num];
			int i = 0;
			while (i < num)
			{
				this.ReadByteOrder(reader);
				WKBReader.CoordinateSystem cs2;
				int srid2;
				WKBGeometryTypes wkbgeometryTypes = this.ReadGeometryType(reader, out cs2, out srid2);
				if (wkbgeometryTypes <= WKBGeometryTypes.WKBGeometryCollectionZ)
				{
					switch (wkbgeometryTypes)
					{
					case WKBGeometryTypes.WKBPoint:
						goto IL_F2;
					case WKBGeometryTypes.WKBLineString:
						goto IL_101;
					case WKBGeometryTypes.WKBPolygon:
						goto IL_111;
					case WKBGeometryTypes.WKBMultiPoint:
						goto IL_121;
					case WKBGeometryTypes.WKBMultiLineString:
						goto IL_131;
					case WKBGeometryTypes.WKBMultiPolygon:
						array[i] = this.ReadMultiPolygon(reader, cs2, srid2);
						break;
					case WKBGeometryTypes.WKBGeometryCollection:
						goto IL_151;
					default:
						switch (wkbgeometryTypes)
						{
						case WKBGeometryTypes.WKBPointZ:
							goto IL_F2;
						case WKBGeometryTypes.WKBLineStringZ:
							goto IL_101;
						case WKBGeometryTypes.WKBPolygonZ:
							goto IL_111;
						case WKBGeometryTypes.WKBMultiPointZ:
							goto IL_121;
						case WKBGeometryTypes.WKBMultiLineStringZ:
							goto IL_131;
						case WKBGeometryTypes.WKBMultiPolygonZ:
							goto IL_161;
						case WKBGeometryTypes.WKBGeometryCollectionZ:
							goto IL_151;
						default:
							goto IL_161;
						}
						break;
					}
				}
				else
				{
					switch (wkbgeometryTypes)
					{
					case WKBGeometryTypes.WKBPointM:
						goto IL_F2;
					case WKBGeometryTypes.WKBLineStringM:
						goto IL_101;
					case WKBGeometryTypes.WKBPolygonM:
						goto IL_111;
					case WKBGeometryTypes.WKBMultiPointM:
						goto IL_121;
					case WKBGeometryTypes.WKBMultiLineStringM:
						goto IL_131;
					case WKBGeometryTypes.WKBMultiPolygonM:
						goto IL_161;
					case WKBGeometryTypes.WKBGeometryCollectionM:
						goto IL_151;
					default:
						switch (wkbgeometryTypes)
						{
						case WKBGeometryTypes.WKBPointZM:
							goto IL_F2;
						case WKBGeometryTypes.WKBLineStringZM:
							goto IL_101;
						case WKBGeometryTypes.WKBPolygonZM:
							goto IL_111;
						case WKBGeometryTypes.WKBMultiPointZM:
							goto IL_121;
						case WKBGeometryTypes.WKBMultiLineStringZM:
							goto IL_131;
						case WKBGeometryTypes.WKBMultiPolygonZM:
							goto IL_161;
						case WKBGeometryTypes.WKBGeometryCollectionZM:
							goto IL_151;
						default:
							goto IL_161;
						}
						break;
					}
				}
				IL_16C:
				i++;
				continue;
				IL_F2:
				array[i] = this.ReadPoint(reader, cs2, srid);
				goto IL_16C;
				IL_101:
				array[i] = this.ReadLineString(reader, cs2, srid2);
				goto IL_16C;
				IL_111:
				array[i] = this.ReadPolygon(reader, cs2, srid2);
				goto IL_16C;
				IL_121:
				array[i] = this.ReadMultiPoint(reader, cs2, srid2);
				goto IL_16C;
				IL_131:
				array[i] = this.ReadMultiLineString(reader, cs2, srid2);
				goto IL_16C;
				IL_151:
				array[i] = this.ReadGeometryCollection(reader, cs2, srid2);
				goto IL_16C;
				IL_161:
				throw new ArgumentException("Should never reach here!");
			}
			return geometryFactory.CreateGeometryCollection(array);
		}

		public bool HandleSRID { get; set; }

		public Ordinates AllowedOrdinates
		{
			get
			{
				return Ordinates.XYZM & this._sequenceFactory.Ordinates;
			}
		}

		public Ordinates HandleOrdinates
		{
			get
			{
				return this._handleOrdinates;
			}
			set
			{
				value = (Ordinates.XY | (this.AllowedOrdinates & value));
				this._handleOrdinates = value;
			}
		}

		public bool RepairRings
		{
			get
			{
				return this._isStrict;
			}
			set
			{
				this._isStrict = value;
			}
		}

		private bool HandleOrdinate(Ordinate ordinate)
		{
			switch (ordinate)
			{
			case Ordinate.X:
			case Ordinate.Y:
				return true;
			case Ordinate.Z:
				return (this.HandleOrdinates & Ordinates.Z) > Ordinates.None;
			case Ordinate.M:
				return (this.HandleOrdinates & Ordinates.M) > Ordinates.None;
			default:
				return false;
			}
		}

		[Obsolete]
		private readonly IGeometryFactory _factory;

		private readonly ICoordinateSequenceFactory _sequenceFactory;

		private readonly IPrecisionModel _precisionModel;

		private readonly IGeometryServices _geometryServices;

		private bool _isStrict;

		private Ordinates _handleOrdinates;

		protected enum CoordinateSystem
		{
			XY = 1,
			XYZ,
			XYM,
			XYZM
		}
	}
}
